using System;
using System.Collections.Generic;
using System.Linq;
using GK;
using Habrador_Computational_Geometry;
using Kamgam.SandGame;
using NoiseGenerator;
using UnityEngine;

namespace MarchingSquares
{
    // Select only parent
    [SelectionBase]
    public class VoxelChunk : MonoBehaviour
    {
        [Header("Single Voxel settings")]

        // Voxel prefab and compute shader
        public ComputeShader marchingCompute;

        public Material voxelMaterial;

        public Material voxelMaterialGpu;

        // Can't touch these
        [HideInInspector] public int chunkX, chunkY;
        [HideInInspector] public Noise noiseGenerator;
        [HideInInspector] public float voxelScale;
        [HideInInspector] public bool showVoxelPointGrid;
        [HideInInspector] public bool useInterpolation;
        [HideInInspector] public bool useUVMapping;
        [HideInInspector] public bool useComputeShader;

        private VoxelData[] _voxelData;
        private int[] _cells; //四个顶点组成,左下角开始
        private MarchingGPU _marchingGPU;
        private MarchingCPU _marchingCPU;
        private PixelWorld _pixelWorld;
        
        private int _chunkResolution;
        private float _chunkSize;

        /// <summary>
        /// voxel 间隔距离
        /// </summary>
        [SerializeField]
        private float _voxelSize;

        private Vector3 _chunkPosition;

        private void Awake()
        {
            _marchingCPU = new MarchingCPU();
            _marchingGPU = new MarchingGPU(marchingCompute);
            
        }

        private void OnDestroy()
        {
            _marchingCPU.Destroy();
            _marchingGPU.Destroy();
        }

        public void Initialize(float chunkSize, int chunkResolution)
        {
            gameObject.name = "Chunk(" + chunkX + "," + chunkY + ")";

            _chunkSize = chunkSize;
            _chunkResolution = chunkResolution;
            _voxelSize = chunkSize / (chunkResolution - 1);

            // Create the voxels arrays
            _voxelData = new VoxelData[chunkResolution * chunkResolution];
            _cells = new int[(chunkResolution - 1) * (chunkResolution - 1)];
            // Caching the transform position
            _chunkPosition = transform.localPosition;

            // Create all the voxels
            int voxelIndex = 0;
            for (int y = 0; y < chunkResolution; y++)
            {
                for (int x = 0; x < chunkResolution; x++)
                {
                    // Set the voxel position
                    _voxelData[voxelIndex].position = Vector3.right * (x * _voxelSize) + Vector3.up * (y * _voxelSize);
                    voxelIndex++;
                }
            }

            // Initialize the current used marching method
            _marchingGPU.Initialize(voxelMaterialGpu, _chunkPosition, chunkResolution, this);
            _marchingCPU.Initialize(voxelMaterial, _chunkPosition, chunkResolution, this);
            _pixelWorld = new PixelWorld();
            _pixelWorld.initBuffer(_voxelSize/2f,chunkResolution,chunkResolution);
        }

        [SerializeField] private PolygonCollider2D _collider;
        public float _colliderTolerance = 1;
        private List<List<Vector2>> _edges = new List<List<Vector2>>();

        void GetTypePoint(int type)
        {
            switch (type)
            {
                case 0:
                    break;
                case 1:

                    break;
                default:
                    break;
            }
        }

        private HashSet<int> _dirtyVertexIdx = new HashSet<int>();

        /// <summary>
        /// 0 d8 0
        /// a1 0 c4
        /// 0 b2 0
        /// </summary>
        /// <param name="index"></param>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <param name="c"></param>
        /// <param name="d"></param>
        List<Vector2> GetVertexOfVoxel(int index)
        {
            bool checkIndex(int i) => i >= 0 && i < _voxelData.Length;

            var pos = _voxelData[index].position;

            Vector2 a = index % _chunkResolution - 1 > 0 ? _voxelData[index - 1].position : pos;
            Vector2 b = checkIndex(index - _chunkResolution) ? _voxelData[index - _chunkResolution].position : pos;
            Vector2 c = index % _chunkResolution + 1 != _chunkResolution ? _voxelData[index + 1].position : pos;
            Vector2 d = checkIndex(index + _chunkResolution) ? _voxelData[index + _chunkResolution].position : pos;
            var tmpColliderPathPoints = new List<Vector2>();
            var type = _voxelData[index].neighborType;

            var UnitsPerPixel = 0.5f;
            float tTop;
            float tRight;
            float tBottom;
            float tLeft;
            var _interpolation = false;

            {
                // No, interpolation. By default are mid edge vertex.
                tTop = 0.5f;
                tRight = 0.5f;
                tBottom = 0.5f;
                tLeft = 0.5f;
            }

            Vector2 top = Vector2.LerpUnclamped(d, pos, tTop);
            Vector2 right = Vector2.LerpUnclamped(c, pos, tRight);
            Vector2 bottom = Vector2.LerpUnclamped(b, pos, tBottom);
            Vector2 left = Vector2.LerpUnclamped(a, pos, tLeft);
//周边是0的记录坐标
            if ((type & 0b0001) == 0)
            {
                tmpColliderPathPoints.Add(left);
            }

            if ((type & 0b0010) == 0)
            {
                tmpColliderPathPoints.Add(bottom);
            }

            if ((type & 0b0100) == 0)
            {
                tmpColliderPathPoints.Add(right);
            }

            if ((type & 0b1000) == 0)
            {
                tmpColliderPathPoints.Add(top);
            }

            return tmpColliderPathPoints;
            // switch (type)
            // {
            //     case 0:
            //     case 15:
            //     case 16:
            //         break;
            //     case 0b0001:
            //         _tmpColliderPathPoints.AddRange(new Vector2[] { bottom, right, top });
            //         break;
            //     case 0b0010:
            //         _tmpColliderPathPoints.AddRange(new Vector2[] { left, top,right});
            //         break;
            //     case 0b0100:
            //         _tmpColliderPathPoints.AddRange(new Vector2[] { top,left , bottom });
            //         break;
            //     case 0b1000:
            //         _tmpColliderPathPoints.AddRange(new Vector2[] { left, bottom, right });
            //         break;
            //     case 0b0011:
            //         _tmpColliderPathPoints.AddRange(new Vector2[] { top, right });
            //         break;
            //     case 0b0110:
            //         _tmpColliderPathPoints.AddRange(new Vector2[] { left, top });
            //         break;
            //     case 0b1010:
            //         _tmpColliderPathPoints.AddRange(new Vector2[] { left, right });
            //         break;
            //     case 0b1100:
            //         _tmpColliderPathPoints.AddRange(new Vector2[] { left, bottom });
            //         break;
            //     case 0b0111:
            //         _tmpColliderPathPoints.Add(top);
            //         break;
            //     case 0b1011:
            //         _tmpColliderPathPoints.Add(right);
            //         break;
            //     case 0b1101:
            //         _tmpColliderPathPoints.Add(bottom);
            //         break;
            //     case 0b1110:
            //         _tmpColliderPathPoints.Add(left);
            //         break;
            //     default:
            //         break;
            // }
        }

        public static List<Vector2> SortVertices(List<Vector2> vertices)
        {
            if (vertices.Count < 3) return vertices;

            // 计算多边形的重心
            Vector2 centroid = CalculateCentroid(vertices);

            // 按相对于重心的极角排序
            return vertices.OrderBy(v => Math.Atan2(v.y - centroid.y, v.x - centroid.x)).ToList();
        }

        private static Vector2 CalculateCentroid(List<Vector2> points)
        {
            float area = 0;
            float centerX = 0;
            float centerY = 0;

            for (int i = 0; i < points.Count; i++)
            {
                Vector2 current = points[i];
                Vector2 next = points[(i + 1) % points.Count];

                float cross = current.x * next.y - next.x * current.y;
                area += cross;
                centerX += (current.x + next.x) * cross;
                centerY += (current.y + next.y) * cross;
            }

            area *= 0.5f;
            float divisor = 6f * area;

            return new Vector2(centerX / divisor, centerY / divisor);
        }

        [ContextMenu("Refresh ColliderJob")]
        public void UpdateColliderJob()
        {
            _pixelWorld.updateWorldColliders(_collider,_voxelData);
        }
        [ContextMenu("Refresh Collider")]
        public void UpdateCollider()
        {
            var delaunayCalculator = new DelaunayCalculator();


            if (_collider is null)
            {
                _collider = gameObject.AddComponent<PolygonCollider2D>();
            }

            _edges.Clear();
            _dirtyVertexIdx.Clear();
            var count = _voxelData.Length;
            for (int i = 0; i < count; i++)
            {
                var voxelIndex = i;
                int indexTop = voxelIndex + _chunkResolution;
                int indexRight = voxelIndex + 1;
                int indexBottom = voxelIndex - _chunkResolution;
                int indexLeft = voxelIndex - 1;
                _voxelData[voxelIndex].neighborType = 0;
                if (_voxelData[voxelIndex].type == 1)
                {
                    if (indexTop >= 0 && indexTop < count && _voxelData[indexTop].type == 1)
                    {
                        _voxelData[voxelIndex].neighborType += 8;
                    }

                    if (indexRight >= 0 && indexRight < count && _voxelData[indexRight].type == 1)
                    {
                        _voxelData[voxelIndex].neighborType += 4;
                    }

                    if (indexBottom >= 0 && indexBottom < count && _voxelData[indexBottom].type == 1)
                    {
                        _voxelData[voxelIndex].neighborType += 2;
                    }

                    if (indexLeft >= 0 && indexLeft < count && _voxelData[indexLeft].type == 1)
                    {
                        _voxelData[voxelIndex].neighborType += 1;
                    }

                    if (_voxelData[voxelIndex].neighborType == 0)
                    {
                        // A filled pixel without any neighbours is code 16.
                        _voxelData[voxelIndex].neighborType = 16;
                    }
                }
            }

            _dfsStartIdx = -1;
            for (int i = 0; i < count; i++)
            {
                var c = i % _chunkResolution;
                var r = i / _chunkResolution;
                var dd = _voxelData[i];
                //计算四个相邻点，得到状态
                //用数组按顺序存储 点索引.
                //todo 跳过孤立点,直接完成碰撞体

                if (_dirtyVertexIdx.Contains(i))
                {
                    continue;
                }

                if (dd.neighborType is 16)
                {
                    _edges.Add(GetVertexOfVoxel(i));
                    _dfsStartIdx++;
                    continue;
                }

                if (dd.neighborType is (0 or 15))
                {
                    continue;
                }


                //todo dfs 头尾换行问题,bfs更加不连续了. 轮廓总是在两个方向上分叉
                _edges.Add(new List<Vector2>());
                _dfsStartIdx++;
                dfs<VoxelData>(_voxelData,
                    (d) =>
                    {
                        if (_dirtyVertexIdx.Contains(d)) return true;
                        if (_voxelData[d].neighborType is (0 or 15 or 16)) return true;

                        return false;
                    },
                    (d) =>
                    {
                        //todo 15时不加点. 按照遍历旋转的顺序添加点.
                        var posList = GetVertexOfVoxel(d);
                        _edges[_dfsStartIdx].AddRange(posList);
                    },
                    _chunkResolution, _chunkResolution, r, c);
            }

            _collider.pathCount = _edges.Count;
            var pathIdx = 0;
            for (var index = 0; index < _edges.Count; index++)
            {
                var edge = _edges[index];
                if (edge.Count <= 0)
                {
                    continue;
                }

                // edge = SortVertices(edge);
                
                var reducedEdge = DouglasPeuckerReduction.DouglasPeuckerReductionPoints(edge, _colliderTolerance);
                var triangleSet =_EarClipping.Triangulate(reducedEdge.ConvertAll(x => new MyVector2(x.x, x.y)));
                foreach (var triangle2 in triangleSet)
                {
                    _collider.pathCount = pathIdx + 1;
                    _collider.SetPath(pathIdx, new List<Vector2>()
                    {
                        triangle2.p1,triangle2.p2,triangle2.p3
                    });
                    pathIdx++;
                }
                // _collider.pathCount = pathIdx + 1;
                // _collider.SetPath(pathIdx, edge);
                // pathIdx++;
            }
        }

        private int _dfsStartIdx;
        private void dfs<T>(IList<T> data, Predicate<int> back, Action<int> action, int width, int height, int r, int c)
        {
            if (r < 0 || r >= width || c < 0 || c >= height) return;
            var index = r * width + c;
            if (back.Invoke(index)) return;
            _dirtyVertexIdx.Add(index);
            action.Invoke(index);
            // 5 4 3
            // 6   2
            // 7 0 1
            dfs(data, back, action, width, height, r - 1, c);
            dfs(data, back, action, width, height, r - 1, c + 1);
            dfs(data, back, action, width, height, r, c + 1);
            dfs(data, back, action, width, height, r + 1, c + 1);
            dfs(data, back, action, width, height, r + 1, c);
            dfs(data, back, action, width, height, r + 1, c - 1);
            dfs(data, back, action, width, height, r, c - 1);
            dfs(data, back, action, width, height, r - 1, c - 1);
        }

        public void Refresh(int chunkRes)
        {
            if (_chunkResolution != chunkRes)
            {
                // Re-initialize the chunk
                Initialize(_chunkSize, chunkRes);
                Debug.Log(("Reinitialize chunks"));
            }

            // Refresh the triangulation
            if (useComputeShader)
            {
                _marchingGPU.Triangulate(ref _voxelData, noiseGenerator.isoLevel, useUVMapping, useInterpolation,
                    _cells);
            }
            else
            {
                _marchingCPU.Triangulate(ref _voxelData, noiseGenerator.isoLevel, useUVMapping, useInterpolation,
                    _cells);
            }
        }

        public void GenVoxelNoise()
        {
            for (int i = 0; i < _voxelData.Length; i++)
            {
                _voxelData[i].value = noiseGenerator.Generate(_voxelData[i].position.x + _chunkPosition.x,
                    _voxelData[i].position.y + _chunkPosition.y);
            }
        }

        public Mesh GetMesh()
        {
            // Return the mesh used by the current used method
            return useComputeShader ? _marchingGPU.mesh : _marchingCPU.mesh;
        }

        private void OnDrawGizmos()
        {
            Gizmos.color = Color.green;
            for (int i = 0; i < _voxelData.Length; i++)
            {
                var pos = transform.TransformPoint(_voxelData[i].position);
                if (_voxelData[i].type == 1)
                {
                    Gizmos.color = Color.green;
                }
                else
                {
                    Gizmos.color = Color.red;
                }
                if (showVoxelPointGrid)
                {
                    Gizmos.DrawWireSphere(pos, 0.1f);
                }
                // if (_voxelData[i].type is 0 or 15)
                // {
                //     continue;
                // }
                //
                //
                // Gizmos.DrawRay(pos, Vector3.up);
            }
        }
    }
}

//三角刨分构造碰撞体，似乎没有有三点共线。
// var tarangs = delaunayCalculator.CalculateTriangulation(edge);
// for (int i = 0; i < tarangs.Triangles.Count/3; i+=3)
// {
//     _collider.pathCount = pathIdx + 1;
//     _collider.SetPath(pathIdx, new List<Vector2>()
//     {
//         tarangs.Vertices[tarangs.Triangles[i]],
//         tarangs.Vertices[tarangs.Triangles[i+1]],
//         tarangs.Vertices[tarangs.Triangles[i+2]],
//     });
//
//     pathIdx++;
// }